/*
 * Copyright (C) 2024 Huawei Device Co., Ltd.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

// 基于setTimeout的Handler实现
// 可设置消息延时响应时间
// 可移除尚未处理的消息
// setTimeout使用的是js的MacroTask事件队列, 为单线程异步 非多线程实现(注意避免耗时操作)
// 除handleMessage外,其他方法不需要重写
export class Handler {
  // record timeout ids, for abort future tasks by removeMessages()/removeCallbacks()
  msgRecords: Map<number, number[]> = new Map
  runnableRecords: Map<() => void, number[]> = new Map

  private updateMsgRecords(what: number, add: boolean, taskId: number) {
    let ids: number[] = this.msgRecords.get(what)
    if (add) {
      if (!ids) {
        ids = []
        this.msgRecords.set(what, ids)
      }
      ids.push(taskId)
    } else {
      if (ids) {
        for (let i = 0; i < ids.length; i++) {
          if (ids[i] === taskId) {
            ids.splice(i, 1)
          }
        }
        if (ids.length === 0) {
          this.msgRecords.delete(what)
        }
      }
    }
  }

  private updateRunnableRecords(r: () => void, add: boolean, taskId: number) {
    let ids: number[] = this.runnableRecords.get(r)
    if (add) {
      if (!ids) {
        ids = []
        this.runnableRecords.set(r, ids)
      }
      ids.push(taskId)
    } else {
      if (ids) {
        for (let i = 0;i < ids.length; i++) {
          if (ids[i] === taskId) {
            ids.splice(i, 1)
          }
        }
        if (ids.length === 0) {
          this.runnableRecords.delete(r)
        }
      }
    }
  }

  private dispatchMessage(msg: Message): void {
    if (msg.callback) {
      msg.callback()
    } else {
      this.handleMessage(msg)
    }
  }

  handleMessage(msg: Message): void {
  }

  sendMessage(msg: Message) {
    this.sendMessageDelayed(msg, 0)
  }

  sendEmptyMessageDelayed(what: number, delay: number) {
    this.sendMessageDelayed(new Message({ what: what }), delay)
  }

  sendEmptyMessage(what: number) {
    this.sendMessage(new Message({ what: what }))
  }

  sendMessageDelayed(msg: Message, delay: number) {
    let taskId: number = setTimeout(() => {
      this.dispatchMessage(msg)
      this.updateMsgRecords(msg.what, false, taskId)
    }, delay)
    this.updateMsgRecords(msg.what, true, taskId)
  }

  removeMessages(what: number) {
    let ids: number[] = this.msgRecords.get(what)
    if (ids) {
      while (ids.length > 0) {
        let taskId = ids.shift()
        clearTimeout(taskId)
      }
      this.msgRecords.delete(what)
    }
  }

  post(r: () => void) {
    this.postDelayed(r, 0)
  }

  postDelayed(r: () => void, delay: number) {
    let taskId = setTimeout(() => {
      if (r) {
        r()
      }
      this.updateRunnableRecords(r, false, taskId)
    }, delay)
    this.updateRunnableRecords(r, true, taskId)
  }

  removeCallbacks(r: () => void) {
    let ids: number[] = this.runnableRecords.get(r)
    if (ids) {
      while (ids.length > 0) {
        let taskId = ids.shift()
        clearTimeout(taskId)
      }
      this.runnableRecords.delete(r)
    }
  }
}

export class Message {
  what: number = 0
  obj: any
  callback: () => void

  constructor(args: {
    what: number,
    obj?: any,
    callback?: () => void
  }) {
    this.what = args.what
    this.obj = args.obj
    this.callback = args.callback
  }
}
